package storage_spec::nfs;

BEGIN {
        if(!exists($::ENV{TRUECL_CODE})) {
                die("Missing TRUECL_CODE environment variable.");
        }
        if(!exists($::ENV{TRUECL_LOG})) {
                die("Missing TRUECL_LOG environment variable.");
        }
        if(!exists($::ENV{TRUECL_CFG})) {
                die("Missing TRUECL_CFG environment variable.");
        }
	push @::INC,"$::ENV{TRUECL_CODE}/lib";
}

use clreq;
use cldb;
use clstatdb;
use Sys::Hostname;
use Data::Dumper;
use mnttab;
use Config;
use clutils;

my $error_msg=undef;

sub get_error_msg {
	return $error_msg;
}

sub new {
my $proto=shift;
my $class=ref($proto) || $proto;
my $self={};

	$self->{TYPE}="Single Image";
	bless($self,$class);
	return $self;
}

sub replicated {
	return 0;
}


sub type {
my $self=shift;
	return $self->{TYPE};
}

# Can define and use a Single image on just one node
sub min_nodes() {
	return 1;
}

# Can used a single image on any of up to 32 nodes
sub max_nodes() {
	return 32;
}

# Can multiple nodes use the resource at the same time without problems?
sub allow_concurrent() {
	return 1;
}

sub repl_ip() {
	return (1,"NONE");
}

#########################################################################
# The below is called via clreqd - and thus must not use clreqd itself!	#
# This assumes that all file systems and logical volumes in use for the	#
# volume group are closed.
#########################################################################

sub run_before_shutdown_on_current($$;$) {
my ($self,$vgname,$lvname)=@_;

	#################################################################
	# NFS does not require any client activity at the device	#
	# level and so we do nothing here.				#
	#################################################################
	return(1,undef);
}

#########################################################################
# The below is called via clreqd - and thus must not use clreqd itself!	#
# So each host will get this called via a storage_spec call for a 	#
# volume group / application.						#
#########################################################################

sub run_before_shutdown_on_non_current($$$;$) {
my ($self,$vgname,$repl_map,$lvname)=@_;
	#################################################################
	# NFS does not require any client activity at the device	#
	# level and so we do nothing here.				#
	#################################################################
	return (1,undef);
}

sub run_before_start_on_current{
my ($self,$vgname,$allow_stale,$force,$lvname)=@_;

	#################################################################
	# NFS does not require any client activity at the device	#
	# level and so we do nothing here.				#
	#################################################################
	return (1,undef);
}

sub run_after_start_on_current($$;$) {
my ($self,$vgname,$lvname)=@_;

	#################################################################
	# NFS does not require any client activity at the device	#
	# level and so we do nothing here.				#
	#################################################################
	return(1,undef);
}

#########################################################################
# NFS can be used as a shared resource, so we do not need to do 	#
# anything to deactivate it at the LV level, but we do un-mount it if	#
# we see it mounted elsewhere [i.e. on this non-current node].		# 
#########################################################################

sub run_before_start_on_non_current($$;$$) {
my ($self,$vgname,$repl_map,$lvname)=@_;

	my $x=new mnttab();
	#################################################################
	# For nfs vgname is NFS and block devi is empty, so get the	#
	# details...							#
	#################################################################

	my $cldb=new cldb("$::ENV{TRUECL_CFG}/config.db");
	if(!defined($cldb)) {
		return(0,"Unable to connect to 'config' database.\nReason: " . (cldb::error)[1]);
	}

	#################################################################
	# Use config database to get all nfs stuff for application.	#
	#################################################################

	my $r=$cldb->get_rows("SELECT * FROM application_fs WHERE name = '$appname';");
	$cldb->disconnect;
	
	for my $r (@$r) {
		next if !defined($r->{lvname});
		my $mnt=$x->get_from_vglv("NFS",$r->{lvname});
		if($mnt) {
			::msg("umount $mnt");
		}
	}
	return(1,undef);
}

#########################################################################
# For an NFS resource we don't need to do anything on the 		#
# non-current nodes after it has been started on current...		#
#########################################################################

sub run_after_start_on_non_current {
my ($self,$vgname,$lvname)=@_;

	return(1,undef);
}

#########################################################################
# Will return (1,undef) - if resources allocated, or (0,errmsg) if not. #
#########################################################################

sub alloc_resources_extras {
	return ();
}

#########################################################################
# This is called on one node and communicates with all the others. The	#
# aim is to allocate resources, though nothing to do for NFS really.	#
#########################################################################

sub alloc_resources($$$$$$;$) {
my ($self,$vgname,$lvname,$appname,$nodelist,$openlocally,%extras)=@_;

	my @N=split(/,/,$nodelist);
	my $clreqdxml=new clreqdxml("$::ENV{TRUECL_CFG}/clreqd.xml");
	if(! defined($clreqdxml)) {
		return (0,"Unable to load Request Daemon configuration info.", "Reason: " . clreqdxml::error());
	}

	my $reqd=new clreq(hosts => [@N], clreqdxml => $clreqdxml, timeout => 5);
	my $results=$reqd->run(cmd => ["echo"]);

	my @OK=(); my @DEAD=();
	for (keys(%$results)) {
		if($results->{$_} =~ /Alive!/) {
			push @OK,$_;
		} else {
			push @DEAD,$_;
		}
	}

	if(@DEAD) {
		my $x=join(",",@DEAD);
		return(0,"Following hosts are not available to allocate resources for: $x");
	}

	#################################################################
	# Since this is share there is nothing to do really ...,  but	#
	# we generate a storage_nfs table, which has the following	#
	# columns;							#
	# name		- application name				#
	# vglv		- lv name [NFS is inferred for VG]		#
	# vischeck	- comma separated list of hosts, currently	#
	# 		- checked as visible on.			#
	#################################################################

	my $cldb=new cldb("$::ENV{TRUECL_CFG}/config.db");
	if(!defined($cldb)) {
		return(0,"Unable to connect to 'config' database.\nReason: " . (cldb::error)[1]);
	}

	my $r=$cldb->exists("table","storage_nfs");
	$cldb->disconnect;
	if($r==0) {
		$r="CREATE TABLE storage_nfs(name VARCHAR(50),vglv VARCHAR(50),vischeck VARCHAR(200));";
		#########################################################
		# use reqd to create on all hosts if necessary.		#
		#########################################################
		my $results=$reqd->run(cmd => ["perform_adhoc_sql","SQL",$r,"DBTYPE","config"]);
	}

	#################################################################
	# The table exists, so indicate that only this node has 	#
	# actually tested visibilty of the device - used for checking	#
	# later...							#
	#################################################################

	my $h=hostname;
	$r="INSERT OR REPLACE INTO storage_nfs (name,vglv,vischeck) VALUES ('$appname','$lvname','$h');";
	$results=$reqd->run(cmd => ["perform_adhoc_sql","SQL",$r,"DBTYPE","config"]);
	return(1,undef);
}

#########################################################################
# Will return (1,undef) - if resources removed, or (0,errmsg) if not. #
#########################################################################

sub remove_resources($$$$$$) {
my ($self,$vgname,$lvname,$appname,$nodelist,$openlocally)=@_;

	my @N=split(/,/,$nodelist);
	my $clreqdxml=new clreqdxml("$::ENV{TRUECL_CFG}/clreqd.xml");
	if(! defined($clreqdxml)) {
		return (0,"Unable to load Request Daemon configuration info.", "Reason: " . clreqdxml::error());
	}

	my $reqd=new clreq(hosts => [@N], clreqdxml => $clreqdxml, timeout => 5);
	my $results=$reqd->run(cmd => ["echo"]);

	my @OK=(); my @DEAD=();
	for (keys(%$results)) {
		if($results->{$_} =~ /Alive!/) {
			push @OK,$_;
		} else {
			push @DEAD,$_;
		}
	}

	if(@DEAD) {
		my $x=join(",",@DEAD);
		return(0,"Following hosts are not available to remove resources from: $x");
	}

	#################################################################
	# Since this is share there is nothing to do really ...,  but	#
	# we remove any entries from the storage_shared table, if	#
	# present.							#
	#################################################################

	my $h=hostname;
	$r="DELETE FROM storage_nfs WHERE name = '$appname' AND vglv = '$lvname';"; 
	if(defined(&::msg)) {
		::msg("Using the following command to remove storage_nfs entry:");
		::msg($r);
	}
	$results=$reqd->run(cmd => ["perform_adhoc_sql","SQL",$r,"DBTYPE","config"]);
	my @F=();
	for (keys(%$results)) {
		if($results->{$_} !~ /^OK/) {
			push @F,$_;
		}
	}
	if(@F) {
		# ::msg(Dumper($results));
		return(0,"Failed to remove entries on hosts: ".join(",",@F));
	}
	return(1,undef);
}

#########################################################################
# The device status can be used to indicate the status of the device on	#
# the specified host. For this storage spec it indicates whether it has	#
# been visisbility checked on the host or not before via the 		#
# storage_nfs table information.					#
#									#
# Returns (0,errmsg) - an error has occured.				#
#         (1,undef)  - validated the device is working as expected	#
#		       on the specified host.				#
#########################################################################

sub device_status($$$$$) {
my ($self,$vg,$lv,$appname,$host)=@_;

	return(1,undef);
}

#########################################################################
# The device name on which the device is mounted - this is just the	#
# lvname in this instance.						#
#########################################################################

sub local_devname($$$$) {
my ($self,$vg,$lv,$appname)=@_;

	return (1,$lv,""); # fsck dev is empty too
}

#########################################################################
# The nfs image type can NOT be be out of sync [only one data copy] -	#
# and hence sync_attempts_supported==0 and hence this routine should	#
# never be called - but is! So always return the details of the logical	#
# volumes, but with a status of 1 for all vols...
#########################################################################

sub sync_status($$$) {
my ($self,$vgname,$appname)=@_;
my @R=();

	#################################################################
	# Get a list of all NFS items since vgname will be nfs...	#
	#################################################################
	my $cldb=new cldb("$::ENV{TRUECL_CFG}/config.db");
	if(!defined($cldb)) {
		return(0,"Unable to connect to 'config' database.\nReason: " . (cldb::error)[1]);
	}

	#################################################################
	# Use config database to get all nfs stuff for application.	#
	#################################################################

	my $r=$cldb->get_rows("SELECT * FROM application_fs WHERE name = '$appname';");
	$cldb->disconnect;
	
	for my $r (@$r) {
		next if !defined($r->{lvname});
		push @R,[$r->{lvname},1,0];
	}
	if(@R) {
		return (1,@R);
	}
	return 1;
}

#########################################################################
# Only a single image, so no sync attempts supported!			#
#########################################################################

sub sync_attempts_supported($) {
my $self=$_[0];

	return 0;
}

#########################################################################
# The below should never be called for this storage type and so		#
# if it is always indicate that the request is successful!!		#
#########################################################################

sub do_sync_attempt($$$$$) {
my ($self,$vgname,$lvname,$appname,$attempt)=@_;

	return (2,undef);
}

#########################################################################
# Indicates that the device in question is currently synched. Can be a 	#
# no-op if necessary, or can update local info - in this case the	#
# storage_drbd1 table in the config database.				#
# (0,errmsg) - error occurred						#
# (1,undef) - OK							#
#########################################################################

sub registered_synched($$$$) {
my ($self,$vgname,$lvname,$appname)=@_;

	return(1,undef);
}

sub registered_unsynched($$$$) {

	return(1,undef);
}

#########################################################################
# Resizing is simple for the nfs client - it can't be done!		#
#########################################################################

sub resize_lv($$$$$$$) {
my  ($self,$vg,$lv,$appname,$running,$nodelist,$oldsize,$newsize)=@_;

	return(99,"NFS does not support client-side file system resize.");
}

#################################################################################
# When we validate we take advantage of the fact that NFS allows the 		#
# file system to be mounted on all the nodes that are to host the application	#
# at the same time, hence for each node we do the following:			#
#										#
# [1] Is file system mounted - if is then indicate node has validated it, and	#
#     move on to the next node.							#
# [2] Attempt to mount the file system and if successful, umount it and mark	#
#     it successful.								#
#################################################################################

sub validate {
my ($self,$application,$vg,$lv,$nlist,$running_on_node)=@_;

	#########################################################################
	# For the nodes in question here we just update the storage_shared	#
	# table since the higher level routine is expected to have validated	#
	# all lv,vgs are same across all nodes.					#
	#########################################################################
	my $clreqdxml=new clreqdxml("$::ENV{TRUECL_CFG}/clreqd.xml");
	if(! defined($clreqdxml)) {
		return (1,"Unable to load Request Daemon configuration info.", "Reason: " . clreqdxml::error());
	}
	my @N=split(/,/,$nlist);
	my $req=new clreq(hosts => [@N], clreqdxml => $clreqdxml, timeout => 5);
	if(!defined($req)) {
		my ($rc,$msg)=clreq::get_error();
		return(1,"Unable to create request object - aborting.\nRC=$rc\nERROR=$msg");
	}
	my $cmd="UPDATE storage_nfs SET vischeck = '$nlist' WHERE name = '$application' AND vglv = '$lv';";
	my $results=$req->run(cmd => ["perform_adhoc_sql","DBTYPE","config","SQL",$cmd]);
	my @e=();
	for (keys(%$results)) {
		if($results->{$_} !~ /^OK/) {
			push @e,$_;
		}
	}
	if(@e) {
		return(1,"Failed to update status of shared storage on nodes: '" . join(",",@e) . "'.");
	}
	return(0,undef);
}

sub min_sync_interval {
	return 20;
}

sub sync_boundaries {
	return (-1,-1);
}

#################################################################
# Return (2,undef) indicating operation is not supported.	#
#################################################################

sub set_sync_rate($$$$$;$) {
	return(2,undef);
}

#################################################################
# We assume that all OS's support a shared storage type.	#
#################################################################

sub supported_os {
	return(1);
}

#################################################################
# drbd has no pre-checks for the node to validate/import on.	#
#################################################################

sub check_use_state($$$$$) {
my ($self,$vgname,$appname,$action,$nodelist)=@_;

	return(0,undef);
}

#################################################################
# Live migration routines not available [this is because drbd	#
# 0.7.x does not support dual primaries.			#
#################################################################

sub support_lmigrate($) {
	return 0;
}

sub onode_premigrate($$$$) {
	return(1,"Functionality not available.");
}

sub nnode_premigrate($$$$) {
	return(1,"Functionality not available.");
}

sub onode_postmigrate($$$$) {
	return(1,"Functionality not available.");
}

sub nnode_postmigrate($$$$) {
	return(1,"Functionality not available.");
}

sub strfmt_logical_short($$) {
my $self=shift;
my $dev=shift;

	return $dev;
}

sub strfmt_logical_long($$) {
my $self=shift;
my $dev=shift;

	return $dev;
}

1;
